package com.flute.framework.search;

import java.io.IOException;

import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.TermDocs;
import org.apache.lucene.search.ComplexExplanation;
import org.apache.lucene.search.Explanation;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.Scorer;
import org.apache.lucene.search.Similarity;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.Weight;
import org.apache.lucene.search.Explanation.IDFExplanation;

public class LayerWeight extends Weight {
	/**
	 * 
	 */
	private static final long serialVersionUID = 5275786521839478322L;
	private TermQuery termQuery;
	private Similarity similarity;
	private float value;
	private float idf;
	private float queryNorm;
	private float queryWeight;
	private IDFExplanation idfExp;

	public LayerWeight(TermQuery query) throws IOException {
		this.termQuery = query;
		this.similarity = new LayerSimilarity();
		idfExp = new IDFExplanation() {
			/**
			 * 
			 */
			private static final long serialVersionUID = 5428139119036728724L;

			@Override
			public String explain() {
				return "idf(1)";
			}

			@Override
			public float getIdf() {
				return 1f;
			}
		};
		
		idf = idfExp.getIdf();
	}

	@Override
	public String toString() {
		return "weight(" + this + ")";
	}

	@Override
	public Query getQuery() {
		return termQuery;
	}

	@Override
	public float getValue() {
		return value;
	}

	@Override
	public float sumOfSquaredWeights() {
		queryWeight = idf * termQuery.getBoost(); // compute query
		// weight
		return queryWeight * queryWeight; // square it
	}

	@Override
	public void normalize(float queryNorm) {
		this.queryNorm = queryNorm;
		queryWeight *= queryNorm; // normalize query
		// weight
		value = queryWeight * idf; // idf for document
	}

	@Override
	public Scorer scorer(IndexReader reader, boolean scoreDocsInOrder,
			boolean topScorer) throws IOException {
		TermDocs termDocs = reader.termDocs(termQuery.getTerm());

		if (termDocs == null)
			return null;

		return new TermScorer(this, termDocs, similarity, null);
	}

	@Override
	public Explanation explain(IndexReader reader, int doc) throws IOException {

		ComplexExplanation result = new ComplexExplanation();
		result.setDescription("weight(" + getQuery() + " in " + doc
				+ "), product of:");

		Explanation expl = new Explanation(idf, idfExp.explain());

		// explain query weight
		Explanation queryExpl = new Explanation();
		queryExpl
				.setDescription("queryWeight(" + getQuery() + "), product of:");

		Explanation boostExpl = new Explanation(termQuery.getBoost(), "boost");
		if (termQuery.getBoost() != 1.0f)
			queryExpl.addDetail(boostExpl);
		queryExpl.addDetail(expl);

		Explanation queryNormExpl = new Explanation(queryNorm, "queryNorm");
		queryExpl.addDetail(queryNormExpl);

		queryExpl.setValue(boostExpl.getValue() * expl.getValue()
				* queryNormExpl.getValue());

		result.addDetail(queryExpl);

		// explain field weight
		String field = termQuery.getTerm().field();
		ComplexExplanation fieldExpl = new ComplexExplanation();
		fieldExpl.setDescription("fieldWeight(" + termQuery.getTerm() + " in "
				+ doc + "), product of:");

		Explanation tfExplanation = new Explanation();
		int tf = 0;
		TermDocs termDocs = reader.termDocs(termQuery.getTerm());
		if (termDocs != null) {
			try {
				if (termDocs.skipTo(doc) && termDocs.doc() == doc) {
					tf = termDocs.freq();
				}
			} finally {
				termDocs.close();
			}
			tfExplanation.setValue(similarity.tf(tf));
			tfExplanation.setDescription("tf(termFreq(" + termQuery.getTerm()
					+ ")=" + tf + ")");
		} else {
			tfExplanation.setValue(0.0f);
			tfExplanation.setDescription("no matching term");
		}
		fieldExpl.addDetail(tfExplanation);
		fieldExpl.addDetail(expl);

		Explanation fieldNormExpl = new Explanation();
		byte[] fieldNorms = reader.norms(field);
		float fieldNorm = fieldNorms != null ? Similarity
				.decodeNorm(fieldNorms[doc]) : 1.0f;
		fieldNormExpl.setValue(fieldNorm);
		fieldNormExpl.setDescription("fieldNorm(field=" + field + ", doc="
				+ doc + ")");
		fieldExpl.addDetail(fieldNormExpl);

		fieldExpl.setMatch(Boolean.valueOf(tfExplanation.isMatch()));
		fieldExpl.setValue(tfExplanation.getValue() * expl.getValue()
				* fieldNormExpl.getValue());

		result.addDetail(fieldExpl);
		result.setMatch(fieldExpl.getMatch());

		// combine them
		result.setValue(queryExpl.getValue() * fieldExpl.getValue());

		if (queryExpl.getValue() == 1.0f)
			return fieldExpl;

		return result;
	}

	public TermQuery getTermQuery() {
		return termQuery;
	}

	public void setTermQuery(TermQuery termQuery) {
		this.termQuery = termQuery;
	}
}
